package handler

import (
	"database/sql"
	"embed"
	"fmt"
	"gitee.com/scottq/go-framework/src/utils"
	"io/ioutil"
	"log"
	"strings"
)

type DbVersionInfo struct {
	Version    string
	SqlContent string
	Comment    string
}

type IVersionMagV2 interface {
	AppendVersionDir(sqlDir string) error
	AppendVersion(v DbVersionInfo)
	AppendSqlContent(version string, content string)
	AppendEmbedFS(fs embed.FS) error
	AllVersionInfo() []DbVersionInfo

	Upgrade(version string) error
	UpgradeOne(vInfo DbVersionInfo) error
	Version() (string, error)

	SetLabel(label string)
	Label() string

	SetVars(map[string]string)

	ILogger
}

type VersionMagV2 struct {
	versions []DbVersionInfo
	db       *sql.DB
	label    string
	vars     map[string]string

	optLock IOptimisticLock

	_logger logFunc
}

func NewDBVersionMagV2(db *sql.DB) (IVersionMagV2, error) {
	lock, err := NewOptimisticLock(db, 60)
	if err != nil {
		return nil, err
	}
	mag := &VersionMagV2{
		db:       db,
		optLock:  lock,
		versions: []DbVersionInfo{},
	}
	if err := mag.init(); err != nil {
		return nil, err
	}
	return mag, nil
}

func (mag *VersionMagV2) init() error {
	var err error

	_, err = mag.db.Exec(CREATE_VERSION_TABLE_SQL)
	if err != nil {
		mag.logError(err)
		return err
	}

	return nil
}

func (mag *VersionMagV2) AddLogger(logger logFunc) {
	mag._logger = logger
}

func (mag *VersionMagV2) AppendVersion(v DbVersionInfo) {
	mag.versions = append(mag.versions, v)
}

func (mag *VersionMagV2) AppendSqlContent(version string, content string) {
	mag.versions = append(mag.versions, DbVersionInfo{
		Version:    version,
		SqlContent: content,
	})
}

func (mag *VersionMagV2) AppendEmbedFS(fss embed.FS) error {
	return utils.ScanEmbedFsDo(fss, func(file string, path string) error {
		bytes, err := fss.ReadFile(path)
		if err != nil {
			return err
		}

		execSql := string(bytes)
		mag.AppendSqlContent(file, execSql)
		return nil
	})
}

func (mag *VersionMagV2) SetVars(vars map[string]string) {
	mag.vars = vars
}

func (mag *VersionMagV2) AppendVersionDir(sqlDir string) error {
	if sqlDir == "" {
		mag.logInfo("path is empty")
		return nil
	}

	if !utils.IsExistPath(sqlDir) {
		mag.logInfo("path not exist: " + sqlDir)
		return nil
	}

	files, err := ioutil.ReadDir(sqlDir)
	if err != nil {
		return err
	}

	//
	for _, x := range files {
		if x.IsDir() {
			continue
		}
		sqlFile := x.Name()
		sqlFileFull := sqlDir + "/" + sqlFile

		upVersion := strings.ReplaceAll(sqlFile, ".sql", "")
		upVersion = strings.TrimLeft(upVersion, "v")

		//执行升级文件
		bytes, err := ioutil.ReadFile(sqlFileFull)
		if err != nil {
			return err
		}

		execSql := string(bytes)
		mag.AppendSqlContent(upVersion, execSql)
	}

	return nil
}

//升级版本
func (mag *VersionMagV2) Upgrade(version string) error {
	if version == "" {
		version = "v99.99.99"
	}
	mag.logInfo("version upgrade to " + version)

	lockName := mag.lockName()
	if !mag.optLock.Lock(lockName) {
		mag.logInfo("upgrade job has locked: " + lockName)
		return nil
	}
	defer mag.optLock.UnLock(lockName)

	//
	versions := mag.versions
	for index, v := range versions {
		upVersion := v.Version

		if utils.CompareVersion(upVersion, version) > 0 {
			continue
		}

		err := mag.UpgradeOne(versions[index])
		if err != nil {
			return err
		}
	}

	return nil
}

func (mag *VersionMagV2) AllVersionInfo() []DbVersionInfo {
	return mag.versions
}

func (mag *VersionMagV2) SetLabel(label string) {
	mag.label = label
}

func (mag *VersionMagV2) Label() string {
	return mag.label
}

func (mag *VersionMagV2) lockName() string {
	return "sql_upgrade." + mag.label
}

func (mag *VersionMagV2) logInfo(info string) {
	if mag._logger == nil {
		log.Println(info)
		return
	}
	mag._logger(info, nil)
}

func (mag *VersionMagV2) logError(err error) {
	if mag._logger == nil {
		log.Println(err.Error())
		return
	}
	mag._logger("", err)
}

//升级一次版本
func (mag *VersionMagV2) UpgradeOne(vInfo DbVersionInfo) error {
	var err error

	version, err := mag.Version()
	if err != nil {
		return err
	}
	upVersion := vInfo.Version

	//无需处理
	if utils.CompareVersion(vInfo.Version, version) <= 0 {
		mag.logInfo("no need upgrade: " + upVersion)
		return nil
	}

	err = mag.upgrade(vInfo)
	if err != nil {
		return err
	}

	mag.logInfo(fmt.Sprintf("upgrade success %s => %s", version, upVersion))

	return nil
}

func (mag *VersionMagV2) upgrade(vInfo DbVersionInfo) error {
	var err error

	sqlContent := vInfo.SqlContent

	if sqlContent == "" {
		return fmt.Errorf("exec upgrade [%s] content is empty", vInfo.Version)
	}

	if mag.vars != nil {
		for k, v := range mag.vars {
			sqlContent = strings.ReplaceAll(sqlContent, k, v)
		}
	}

	_, err = mag.db.Exec(sqlContent)
	if err != nil {
		mag.logError(err)
		return err
	}

	//保存version信息
	err = mag.addVersionRecord(mag.label, vInfo.Version, vInfo.Comment)
	if err != nil {
		mag.logError(err)
		return err
	}

	return nil
}

//获取当前版本信息
func (mag VersionMagV2) Version() (string, error) {
	var err error

	querySql := fmt.Sprintf("SELECT version FROM %s WHERE label=? ORDER BY id DESC LIMIT 1", VERSION_MAG_TABLE)
	row := mag.db.QueryRow(querySql, mag.label)
	var version string
	err = row.Scan(&version)
	switch err {
	case nil:
	case sql.ErrNoRows:
	default:
		mag.logError(err)
		return "", err
	}

	if version == "" {
		version = "0.0.0"
	}

	return version, nil
}

func (mag *VersionMagV2) addVersionRecord(label, version, sqlFile string) error {

	//保存version信息
	insertSql := fmt.Sprintf("INSERT INTO %s SET `label`=?,`version`=?,`upgrade_file`=?", VERSION_MAG_TABLE)
	stmt, err := mag.db.Prepare(insertSql)
	if err != nil {
		mag.logError(err)
		return err
	}
	_, err = stmt.Exec(label, version, sqlFile)
	if err != nil {
		mag.logError(err)
		return err
	}

	return nil
}
